import { mat4, vec3 } from "wgpu-matrix";
import { type Base } from "../Base";
import {
  cubeVertexArray,
  cubeVertexSize,
  cubePositionOffset,
  cubeUVOffset,
  cubeVertexCount,
} from "../../meshes/cube";
import basicVertWGSL from "../../shaders/basic.vert.wgsl";
import vertexPositionColorWGSL from "../../shaders/vertexPositionColor.frag.wgsl";
import { quitIfWebGPUNotAvailable } from "../../utils";
class TwoCubes implements Base {
  async run(el: HTMLCanvasElement) {
     const adapter = await navigator.gpu?.requestAdapter({
       featureLevel: "compatibility",
     });
     const device = await adapter?.requestDevice();
     if (!device) {
       console.error("WebGPU not supported");
       return;
     }
     quitIfWebGPUNotAvailable(adapter, device);
     const context = el.getContext("webgpu") as GPUCanvasContext | null;
   
     if (!context) {
       console.error("WebGPU context not supported");
       return;
     }
     const devicePixelRatio = window.devicePixelRatio;
     el.width = el.clientWidth * devicePixelRatio;
     el.height = el.clientHeight * devicePixelRatio;
     const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
   
     context.configure({
       device,
       format: presentationFormat,
     });
    // Create a render pipeline, shaders, and other resources here.
    // This is where you would set up your two cubes.
    console.log("WebGPU initialized with two cubes setup.");
    // Example: You might create two cube buffers, set up a render pass, etc.
    // Note: The actual rendering code is not provided here.
    // You would typically use the cubeVertexArray and other constants defined in cube.ts
    // to create and bind buffers and other resources.
    // For example:
    // const cubeBuffer1 = device.createBuffer({...});
    // const cubeBuffer2 = device.createBuffer({...});
    // const renderPassDescriptor = {...};
    // const renderPass = commandEncoder.beginRenderPass(renderPassDescriptor);
    // renderPass.setPipeline(renderPipeline);
    // renderPass.setVertexBuffer(0, cubeBuffer1);
    // renderPass.draw(cubeVertexCount);
    // renderPass.setVertexBuffer(0, cubeBuffer2);
    // renderPass.draw(cubeVertexCount);
    // renderPass.end();
    // Note: The above code is just a placeholder and does not represent actual WebGPU API usage.
    console.log("Two cubes setup complete.");
    const verticesBuffer = device.createBuffer({
      size: cubeVertexArray.byteLength,
      usage: GPUBufferUsage.VERTEX,
      mappedAtCreation: true,
    });

    new Float32Array(verticesBuffer.getMappedRange()).set(cubeVertexArray);
    verticesBuffer.unmap();

    const pipeline = device.createRenderPipeline({
      layout: "auto",
      vertex: {
        module: device.createShaderModule({
          code: basicVertWGSL,
        }),
        buffers: [
          {
            arrayStride: cubeVertexSize,
            attributes: [
              {
                format: "float32x4",
                offset: cubePositionOffset,
                shaderLocation: 0,
              }, // position
              {
                format: "float32x2",
                offset: cubeUVOffset,
                shaderLocation: 1,
              }, // uv
            ],
          },
        ],
      },
      fragment: {
        module: device.createShaderModule({
          code: vertexPositionColorWGSL,
        }),
        targets: [
          {
            format: presentationFormat,
          },
        ],
      },
      primitive: {
        topology: "triangle-list",
        cullMode: "back",
      },
      depthStencil: {
        format: "depth24plus",
        depthWriteEnabled: true,
        depthCompare: "less",
      },
    });

    const texture = device.createTexture({
      size: [el.width, el.height],
      format: "depth24plus",
      usage: GPUTextureUsage.RENDER_ATTACHMENT,
    });

    const matrixSize = 4 * 16; // 4x4 matrix
    const offset = 256; // uniformBindGroup offset must be 256-byte aligned
    const uniformBufferSize = offset + matrixSize;
    const uniformBuffer = device.createBuffer({
      size: uniformBufferSize,
      usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
    });
    const uniformBindGroup1 = device.createBindGroup({
      layout: pipeline.getBindGroupLayout(0),
      entries: [
        {
          binding: 0,
          resource: {
            buffer: uniformBuffer,
            offset: 0,
            size: matrixSize,
          },
        },
      ],
    });
    const uniformBindGroup2 = device.createBindGroup({
      layout: pipeline.getBindGroupLayout(0),
      entries: [
        {
          binding: 0,
          resource: {
            buffer: uniformBuffer,
            offset: offset,
            size: matrixSize,
          },
        },
      ],
    });

    const renderPassDescriptor: GPURenderPassDescriptor = {
      colorAttachments: [
        {
          view: undefined as unknown as GPUTextureView, // Will be set later
          clearValue: [0.5, 0.5, 0.5, 1.0], // Clear color
          loadOp: "clear",
          storeOp: "store",
        },
      ] as GPURenderPassColorAttachment[],
      depthStencilAttachment: {
        view: texture.createView(),
        depthClearValue: 1.0,
        depthLoadOp: "clear",
        depthStoreOp: "store",
      },
    } as unknown as GPURenderPassDescriptor;

    const aspect = el.width / el.height;
    const projectionMatrix = mat4.perspective(
      (2 * Math.PI) / 5,
      aspect,
      1,
      100.0
    );

    const modelMatrix1 = mat4.translation(vec3.create(-2, 0, 0));
    const modelMatrix2 = mat4.translation(vec3.create(2, 0, 0));
    const modelViewProjectionMatrix1 = mat4.create();
    const modelViewProjectionMatrix2 = mat4.create();
    const viewMatrix = mat4.translation(vec3.fromValues(0, 0, -7));

    const tmpMat41 = mat4.create();
    const tmpMat42 = mat4.create();

    function updateTransformationMatrix() {
      const now = Date.now() / 1000;

      mat4.rotate(
        modelMatrix1,
        vec3.fromValues(Math.sin(now), Math.cos(now), 0),
        1,
        tmpMat41
      );
      mat4.rotate(
        modelMatrix2,
        vec3.fromValues(Math.cos(now), Math.sin(now), 0),
        1,
        tmpMat42
      );

      mat4.multiply(viewMatrix, tmpMat41, modelViewProjectionMatrix1);
      mat4.multiply(
        projectionMatrix,
        modelViewProjectionMatrix1,
        modelViewProjectionMatrix1
      );
      mat4.multiply(viewMatrix, tmpMat42, modelViewProjectionMatrix2);
      mat4.multiply(
        projectionMatrix,
        modelViewProjectionMatrix2,
        modelViewProjectionMatrix2
      );
    }

    function frame() {
      if (!device) {
        console.error("Device is undefined.");
        return;
      }
      updateTransformationMatrix();
      device.queue.writeBuffer(
        uniformBuffer,
        0,
        modelViewProjectionMatrix1.buffer,
        modelViewProjectionMatrix1.byteOffset,
        modelViewProjectionMatrix1.byteLength
      );
      device.queue.writeBuffer(
        uniformBuffer,
        offset,
        modelViewProjectionMatrix2.buffer,
        modelViewProjectionMatrix2.byteOffset,
        modelViewProjectionMatrix2.byteLength
      );
      
      // @ts-ignore
      renderPassDescriptor.colorAttachments[0].view = context
        .getCurrentTexture()
        .createView();

      const commandEncoder = device.createCommandEncoder();
      const passEncoder = commandEncoder?.beginRenderPass(renderPassDescriptor);
      passEncoder?.setPipeline(pipeline);
      passEncoder?.setVertexBuffer(0, verticesBuffer);

      // Bind the bind group (with the transformation matrix) for
      // each cube, and draw.
      passEncoder?.setBindGroup(0, uniformBindGroup1);
      passEncoder?.draw(cubeVertexCount);

      passEncoder?.setBindGroup(0, uniformBindGroup2);
      passEncoder?.draw(cubeVertexCount);

      passEncoder?.end();
      if (commandEncoder) {
        device.queue.submit([commandEncoder.finish()]);
      }

      requestAnimationFrame(frame);
    }
    requestAnimationFrame(frame);
  }
}
export default TwoCubes;
